
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** @author  John Miller
 *  @version 1.0
 *  @date    Sat Aug 20 23:20:26 EDT 2011
 *  @see     LICENSE (MIT style license file).
 */

package scalation.maxima

import util.control.Breaks.{breakable, break}

import scalation.linalgebra.{MatrixD, VectorD}
import scalation.util.Error

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** This class solves Linear Programming (LP) problems using the Revised Simplex
 *  Algorithm.  Given a constraint matrix a, constant vector b and cost vector c,
 *  find values for the solution/decision vector x that maximize the objective
 *  function f(x), while satisfying all of the constraints, i.e.,
 *
 *  maximize    f(x) = c x
 *  subject to  a x <= b, x >= 0
 *
 *  The revised algorithm has benefits over the Simplex Algorithm (less memory
 *  and reduced chance of round off errors).
 *  @see math.uc.edu/~halpern/Linear.progr.folder/Handouts.lp.02/Revisedsimplex.pdf
 *  @param a      the constraint matrix
 *  @param b      the constant/limit vector
 *  @param c      the cost/revenue vector
 *  @param x_B    the initial basis (set of indices where x_i is in the basis)
 */
class RevisedSimplex (a: MatrixD, b: VectorD, c: VectorD, var x_B: Array [Int] = null)
      extends Error
{
    private val EPSILON  = 1E-9                         // number close to zero
    private val M        = a.dim1                       // number of constraints (rows in a)
    private val N        = a.dim2                       // number of original variables (columns in a)
    private val MAX_ITER = 200 * N                      // maximum number of iterations

    if (b.dim != M) flaw ("constructor", "b.dim = " + b.dim + " != " + M)
    if (c.dim != N) flaw ("constructor", "c.dim = " + c.dim + " != " + N)

    private val b_inv    = a.selectCols (x_B).inverse   // basis of matrix-a inverted

    private var c_       = c.select (x_B) * b_inv       // adjusted cost
    private val b_       = b_inv * b                    // adjusted constants

    private var u: VectorD = null                       // ?? FIX
    private var z: VectorD = null                       // ?? FIX

    if (x_B == null) x_B = setBasis ()

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** There are M+N variables, N decision and M slack variables, of which,
     *  for each iteration, M are chosen for a Basic Feasible Solution (BFS).
     *  The the variables not in the basis are set to zero.  Setting j to N
     *  will start with the slack variables in the basis (only works if b >= 0).
     *  @param j  the offset to start the basis
     *  @param l  the size of the basis
     */
    def setBasis (j: Int = N, l: Int = M): Array [Int] =
    {
        val ba = Array.ofDim [Int] (l)
        for (i <- 0 until l) ba(i) = i + j
        ba
    } // setBasis

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Find the best variable x_l to enter the basis.
     */
    def entering (): Int = 
    {
        z = c_ * a - c
        z.argmaxPos ()
    } // entering 

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Find the best variable x_k to leave the basis given that x_l is entering.
     *  @param l  the variable chosen to enter the basis
     */
    def leaving (l: Int): Int =
    {
        u = b_inv * a.col(l)
        if (unbounded (u)) return -1
        var k = 0
        var r_min = Double.PositiveInfinity
        for (i <- 0 until M if u(i) > 0) {
            val r = b_(i) / u(i)
            if (r < r_min) { r_min = r; k = i}
        } // for
        k
    } // leaving

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Pivot by replacing x_k with x_l in the basis.  Update b_inv, b_ and c_.
     *  @param k  the leaving variable
     *  @param l  the entering variable
     */
    def pivot (k: Int, l: Int)
    {
        x_B(k) = l                // update basis (l replaces k)
        b_inv(k) /= u(k)
        b_(k)    /= u(k)
        for (i <- 0 until M if i != k) {
            b_inv(i) -= b_inv(k) * u(i)
            b_ (i)   -= b_(k) * u(i)
        } // for
        c_ -= b_inv(k) * z(l)
    } // pivot

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** if u <= 0., the solution is unbounded.
     *  @param u  the ?? vector FIX
     */
    def unbounded (u: VectorD): Boolean =
    {
        for (i <- 0 until u.dim if u(i) > 0.) return false
        println ("the solution is unbounded")
        true
    } // unbounded

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Solve a Linear Programming (LP) problem using the Revised Simplex Algorithm.
     *  Iteratively pivot until there an optimal solution is found or it is
     *  determined that the solution is unbounded.
     */
    def solve (): Tuple2 [VectorD, Double] =
    {
        var k = -1       // the leaving variable
        var l = -1       // the entering variable
        showTableau

        breakable { for (it <- 1 to MAX_ITER) {
            l = entering (); if (l == -1) break    // optimal solution found
            k = leaving (l); if (k == -1) break    // solution is unbounded
            pivot (k, l)                           // pivot: k leaves and l enters
            showTableau
        }} // for

        val x = primal
        (x, objValue (x))    // return the primal solution and the optimal value
    } // solve

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Return the primal solution vector (x).
     */
    def primal: VectorD = b_inv * b

   //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Return the dual solution vector (y).  FIX
     */
    def dual: VectorD = null

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Return the optimal objective function value (f(x) = c x).
     *  @param x  the primal solution vector
     */
    def objValue (x: VectorD): Double = c.select(x_B) dot x

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    /** Show the current revised tableau displaying the basis, b_inv, b_, c_.
     */
    def showTableau
    {
        println ("Revised Tableau")
        println ("---------------------------------------------------")
        for (i <- 0 until a.dim1) {
            println ("x" + x_B(i) + " | " + b_inv(i) + " | " + b_(i))
        } // for
        println ("c_ | " + c_)
        println ("---------------------------------------------------")
    } // showTableau

} // RevisedSimplex class


//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** Test the Revised Simplex Algorithm class with the following maximization problem:
 *  Maximize    z = 2x_0 + 3x_1 + 4x_2
 *  Subject to      3x_0 + 2x_1 + 1x_2 + 1y_3 + 0y_4 = 10
 *                 2x_0 + 5x_1 + 3x_2 + 0y_3 + 1y_4 = 15
 *  where z is the objective variable, x are the decision variables and
 *  y are slack variables.
 */
object RevisedSimplexTest extends App
{
    // initialize matrix a, vectors b and c and the basis
    //
    val a = new MatrixD ((3, 7), 0.,  3.,  3.,  1., -1., 1., -5,
                                 1.,  3., -1.,  0., -1., 1.,  3., 
                                 1.,  2.,  0.,  1.,  2., 0., -2.)
    val c   = new VectorD      (-2., -3.,  4., -3., -1., 4., -6.)
    val b   = new VectorD (3., 4., 5.)
    val x_B = Array (3, 5, 0)

    val result = (new RevisedSimplex (a, b, c, x_B)).solve ()

    println ("---------------------------------------------------")
    println ("optimal x = " + result._1 + ", max cx = " + result._2)
    println ("---------------------------------------------------")

/*
    // initialize matrix a and vectors b and c
    //
    val a = new MatrixD ((2, 3), 3., 2., 1.,
                                 2., 5., 3.)
    val b   = new VectorD (10., 15.)
    val c   = new VectorD (2., 3., 4.)
    val x_B = Array (1, 2)

    val result = (new RevisedSimplex (a, b, c, x_B)).solve ()

    println ("---------------------------------------------------")
    println ("optimal x = " + result._1 + ", max cx = " + result._2)
    println ("---------------------------------------------------")
*/

} // RevisedSimplexTest object

